"""
在金融市场的投资策略领域中，均值回归策略是一种基于统计学原理和市场规律的经典量化交易策略，其核心思想源于对价格波动特性的观察与分析。
均值回归的基本概念建立在统计学中的均值理论之上。
从长期来看，资产价格往往围绕着某个 “均值” 上下波动，当价格偏离这个均值达到一定程度时，后续有较大概率会向均值方向回归。
就如同被拉伸的弹簧，在失去外力作用后，会自然恢复到原始状态，金融市场中的资产价格也存在类似的 “恢复力” ，推动价格向均值靠拢。
均值回归策略的原理在于，通过对历史数据的分析计算出资产价格的均值，并结合标准差等统计指标，设定价格偏离均值的阈值。
当资产价格高于均值一定标准差倍数时，判定为超买状态，预示价格可能下跌，此时可考虑卖出资产；
反之，当价格低于均值一定标准差倍数时，认定为超卖状态，意味着价格可能反弹，适合买入资产。
在代码中，lookback_period参数设定了计算均值和标准差的时间窗口，num_std则确定了价格偏离均值的倍数阈值，通过这些参数的合理设置，实现对市场信号的捕捉。
该策略在金融市场具有广泛的应用场景。在股票市场中，可用于筛选处于超买或超卖状态的个股，帮助投资者把握买卖时机；
同时，均值回归策略还可与其他策略相结合，构建更为复杂和稳健的投资组合。
然而，均值回归策略并非完美无缺，也存在一定的局限性。
市场环境复杂多变，有时价格可能会持续偏离均值，出现 “趋势行情”，此时均值回归策略可能失效，导致投资者产生损失。
此外，参数的选择对策略效果影响显著，不同的lookback_period和num_std设置可能带来截然不同的交易结果，需要投资者根据市场情况不断优化调整。
"""
import pandas as pd
from sqlalchemy import create_engine
import matplotlib.pyplot as plt
from datetime import datetime
import numpy as np

# 创建数据库连接引擎
engine = create_engine('mysql+pymysql://root:@127.0.0.1:3306/stocks?charset=utf8mb4&use_unicode=1')

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


def generate_mean_reversion_signals(lookback_period=60, num_std=2):
    """
    完全自适应布局的均值回归策略信号表格
    确保标题、时间戳和表格内容完整显示
    """
    # 获取股票数据
    query = "SELECT ts_code, name FROM stocks_info"
    stocks = pd.read_sql(query, con=engine)

    data = []
    for _, stock in stocks.iterrows():
        ts_code = stock['ts_code']
        name = stock['name']

        try:
            query = f"""
                SELECT trade_date, close 
                FROM stocks_daily 
                WHERE ts_code = '{ts_code}' 
                ORDER BY trade_date DESC 
                LIMIT {lookback_period + 10}
            """
            df = pd.read_sql(query, con=engine)

            if len(df) < lookback_period:
                signal = f"数据不足({lookback_period}天)"
            else:
                df = df.sort_values('trade_date')
                mean = df['close'].rolling(window=lookback_period).mean().iloc[-1]
                price = df['close'].iloc[-1]

                if price <= mean - num_std * df['close'].rolling(window=lookback_period).std().iloc[-1]:
                    signal = f"买入\n均值:{mean:.2f} 现价:{price:.2f}"
                elif price >= mean + num_std * df['close'].rolling(window=lookback_period).std().iloc[-1]:
                    signal = f"卖出\n均值:{mean:.2f} 现价:{price:.2f}"
                elif price > mean:
                    signal = f"持有\n均值:{mean:.2f} 现价:{price:.2f}"
                else:
                    signal = f"观望\n均值:{mean:.2f} 现价:{price:.2f}"

            data.append([name, ts_code, signal])

        except Exception as e:
            data.append([name, ts_code, f"错误\n{str(e)}"])

    df_result = pd.DataFrame(data, columns=['股票名称', '股票代码', '信号'])

    # 动态计算图形尺寸
    num_rows = len(df_result)
    row_height = 0.7  # 每行高度(英寸)
    header_height = 1.2  # 表头高度(英寸)
    footer_space = 0.8  # 底部留白(英寸)
    title_space = 1.5  # 标题区域高度(英寸)

    fig_height = min(
        title_space + header_height + num_rows * row_height + footer_space,
        30  # 最大高度限制
    )

    # 创建图形
    fig = plt.figure(figsize=(12, fig_height))
    ax = fig.add_subplot(111)
    ax.axis('off')

    # 添加标题和时间戳
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    title = fig.suptitle(
        f'股票均值回归策略信号 ({lookback_period}日, {num_std}σ)\n生成时间: {current_time}',
        y=0.95,  # 标题位置
        fontsize=14,
        fontweight='bold'
    )

    # 计算表格位置(动态调整)
    table_top = 0.88 - (0.01 * num_rows)  # 根据行数动态调整
    table_height = 0.85 - (0.005 * num_rows)

    # 创建表格
    table = ax.table(
        cellText=df_result.values,
        colLabels=df_result.columns,
        loc='center',
        bbox=[0.1, 0.05, 0.8, table_height],  # 左右留白10%，高度自适应
        cellLoc='center'
    )

    # 设置表格样式
    table.auto_set_font_size(False)
    table.set_fontsize(10)

    # 设置行高
    for i in range(len(df_result) + 1):
        for j in range(3):
            cell = table[(i, j)]
            cell.set_height(0.08)
            cell.set_edgecolor('lightgray')

    # 设置表头样式
    for j in range(3):
        table[(0, j)].set_facecolor('#40466e')
        table[(0, j)].get_text().set_color('white')
        table[(0, j)].set_fontsize(11)

    # 设置信号单元格颜色
    color_map = {
        '买入': '#ff6b6b',
        '卖出': '#51cf66',
        '观望': '#339af0',
    }

    for i in range(1, len(df_result) + 1):
        signal = df_result.iloc[i - 1]['信号']
        color = 'white'
        for key, val in color_map.items():
            if key in signal:
                color = val
                break

        for j in range(3):
            table[(i, j)].set_facecolor(color)
            if color != 'white':
                table[(i, j)].get_text().set_color('white')

    # 确保所有内容可见
    plt.tight_layout(rect=[0, 0, 1, 0.93])  # 调整布局区域
    plt.subplots_adjust(top=0.88, bottom=0.05)  # 精确控制边距

    plt.show()


# 生成信号表格
generate_mean_reversion_signals()